/**
 * 
 */
package edu.pw.treegrid.server.query;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.pw.treegrid.server.filter.Filter;
import edu.pw.treegrid.server.reportmodel.ReportColumn;

/** 
 * @author Piotrek
 */
public class QueryTree {

	private static int COLUMN_ALIAS_SEQUENCE = 1;
	
	private TableNode rootTable;

	public TableNode getRootTable() {
		return rootTable;
	}

	public void setRootTable(TableNode rootTable) {
		this.rootTable = rootTable;
		generateTableNumber(rootTable);
	}

	private void generateTableNumber(TableNode table) {
		table.setNumber(getTableNumber(table.getTableName()));
	}

	/**
	 * Kolumny wystepujace w klauzuli GROUP BY
	 */
	private List<ReportColumn> groupByColumns = new LinkedList<ReportColumn>();

	public List<ReportColumn> getGroupByColumns() {
		return groupByColumns;
	}

	public void setGroupByColumns(List<ReportColumn> groupByColumns) {
		this.groupByColumns = groupByColumns;
	}

	private Filter filter;

	public Filter getFilter() {
		return filter;
	}
	
	public void setFilter(Filter filter) {
		this.filter = filter;
	}

	public Query generateQuery() {
		Query q = new Query();
		if (!groupByColumns.isEmpty()) {
			removeNotGroupByColumns(rootTable);
			generateGroupBy(q);
		}
		if (filter != null) {
			StringBuilder sb = new StringBuilder();
			filter.generateWhere(this, sb);
			q.setWhere(sb.toString());
		}
		rootTable.generateQuery(q);
		return q;
	}

	/**
	 * Usuwa z tabeli kolumny, ktore nie wystepuja w klauzuli WHERE
	 * @param table
	 */
	private void removeNotGroupByColumns(TableNode table) {
		Iterator<Node> iterator = table.getChilds().iterator();
		while (iterator.hasNext()) {
			Node c = iterator.next();
			if (c instanceof ColumnNode) {
				ColumnNode cn = (ColumnNode) c;
				if (!isGroupByColumnNode(cn)) {
					cn.setGroupBy(false);
				}
			} else {
				TableNode tn = (TableNode) c;
				removeNotGroupByColumns(tn);
			}
		}
	}

	private boolean isGroupByColumnNode(ColumnNode cn) {
		for (ReportColumn rc : getGroupByColumns()) {
			//if (cn.matchesGroupColumn(rc)) {
			if(this.reportColumnToColumNodes.get(rc).contains(cn)) {
				return true;
			}
			
		}
		
		return false;
	}

	private void generateGroupBy(Query q) {
		StringBuilder sb = new StringBuilder();
		for (ColumnNode cn : rootTable.getLeafs()) {
			if(cn.isGroupBy()) {
				if (sb.length() > 0) {
					sb.append(", ");
				}
				sb.append("\n\t");
				sb.append(cn.getExpressionString());
			}
		}
		q.setGroupBy(sb.toString());
	}

	/**
	 * @param reportColumn
	 */
	public void mergeWith(ReportColumn reportColumn) {
		Node node = reportColumn.buildQueryNode();
		Set<ColumnNode> columns = node.getLeafs();
		for(ColumnNode c : columns) {
			String alias = "C" + Integer.toString(COLUMN_ALIAS_SEQUENCE++);
			c.setColumnAlias(alias);
			columnAliasToReportColumn.put(alias, reportColumn);
		}
		reportColumnToColumNodes.put(reportColumn, columns);
		mergeWithR(rootTable, node);
	}

	private Map<String, ReportColumn> columnAliasToReportColumn = new HashMap<String, ReportColumn>();
	private Map<ReportColumn, Set<ColumnNode>> reportColumnToColumNodes = new HashMap<ReportColumn, Set<ColumnNode>>();
	
	public Set<ColumnNode> getColumnNodesForReportColumn(ReportColumn reportColumn) {
		return reportColumnToColumNodes.get(reportColumn);
	}

	public Map<ReportColumn, Set<ColumnNode>> getReportColumnToColumNodes() {
		return reportColumnToColumNodes;
	}
	
	public List<ColumnNode> getNotIdColumnNodes() {
		List<ColumnNode> result = new LinkedList<ColumnNode>();
		
		for(Set<ColumnNode> cols : reportColumnToColumNodes.values()) {
			for(ColumnNode c : cols) {
				if(!c.isIdColumn()) {
					result.add(c);
				}
			}
		}
		
		return result;
	}
	
	private Map<String, Integer> tableNumber = new HashMap<String, Integer>();
	
	private Integer getTableNumber(String tableName) {
		Integer result = 1;
		if(tableNumber.containsKey(tableName)) {
			result = tableNumber.get(tableName);
			result++;
		}
		tableNumber.put(tableName, result);
		return result;
	}
	
	private void mergeWithR(TableNode parent, Node node) {
		Node child = null;

		for (Node c : parent.getChilds()) {
			//if (c.equals(node)) {
			if(c.matches(node)) {
				child = c;
				break;
			}
		}

		if (child == null) {
			parent.addChild(node);
			addR(parent, node);
		} else if (child instanceof TableNode) {
			for (Node c : ((TableNode) node).getChilds()) {
				mergeWithR((TableNode) child, c);
			}
		}
	}

	private void addR(TableNode parent, Node node) {
		if(node instanceof TableNode) {
			TableNode tn = (TableNode)node;
			generateTableNumber(tn);
			for(Node c : tn.getChilds()) {
				addR(tn, c);
			}
		}
		
	}

	public void displayTree() {
		System.out.print("\n--== QueryTree ==--");
		displayTreeR(rootTable, 0);
		System.out.print("\n");
	}

	private void displayTreeR(Node node, int level) {
		System.out.print("\n");
		for (int i = 0; i < level; ++i) {
			System.out.print("--");
		}
		System.out.print("* ");
		if (node instanceof ColumnNode) {
			ColumnNode cn = (ColumnNode) node;
			System.out.print(cn.getExpression() + "->" + cn.getColumnAlias());
			if(cn.isGroupBy()) {
				System.out.print(" G");
			}
			if(cn.isMeasure()) {
				System.out.print(" M");
			}
		} else {
			TableNode tn = (TableNode) node;
			System.out.print(tn.getTableAlias());
			for (Node c : tn.getChilds()) {
				displayTreeR(c, level + 1);
			}
		}
	}


	public ReportColumn getReportColumnByAlias(String column) {
		return this.columnAliasToReportColumn.get(column);
	}

	private Map<Integer, Object> parameters = new HashMap<Integer, Object>();
	
	public void addParameter(Object value) {
		int currentNumber = parameters.size() + 1;
		this.parameters.put(currentNumber, value);
	}

	public Map<Integer, Object> getParameters() {
		return parameters;
	}
}